<!DOCTYPE html>

<html>

<head>

<meta charset="utf-8" />
<meta name="generator" content="pandoc" />
<meta http-equiv="X-UA-Compatible" content="IE=EDGE" />

<meta name="viewport" content="width=device-width, initial-scale=1" />



<title>Using renv with Docker</title>

<script>// Pandoc 2.9 adds attributes on both header and div. We remove the former (to
// be compatible with the behavior of Pandoc < 2.8).
document.addEventListener('DOMContentLoaded', function(e) {
  var hs = document.querySelectorAll("div.section[class*='level'] > :first-child");
  var i, h, a;
  for (i = 0; i < hs.length; i++) {
    h = hs[i];
    if (!/^h[1-6]$/i.test(h.tagName)) continue;  // it should be a header h1-h6
    a = h.attributes;
    while (a.length > 0) h.removeAttribute(a[0].name);
  }
});
</script>

<style type="text/css">
code{white-space: pre-wrap;}
span.smallcaps{font-variant: small-caps;}
span.underline{text-decoration: underline;}
div.column{display: inline-block; vertical-align: top; width: 50%;}
div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
ul.task-list{list-style: none;}
</style>



<style type="text/css">
code {
white-space: pre;
}
.sourceCode {
overflow: visible;
}
</style>
<style type="text/css" data-origin="pandoc">
html { -webkit-text-size-adjust: 100%; }
pre > code.sourceCode { white-space: pre; position: relative; }
pre > code.sourceCode > span { display: inline-block; line-height: 1.25; }
pre > code.sourceCode > span:empty { height: 1.2em; }
.sourceCode { overflow: visible; }
code.sourceCode > span { color: inherit; text-decoration: inherit; }
div.sourceCode { margin: 1em 0; }
pre.sourceCode { margin: 0; }
@media screen {
div.sourceCode { overflow: auto; }
}
@media print {
pre > code.sourceCode { white-space: pre-wrap; }
pre > code.sourceCode > span { text-indent: -5em; padding-left: 5em; }
}
pre.numberSource code
{ counter-reset: source-line 0; }
pre.numberSource code > span
{ position: relative; left: -4em; counter-increment: source-line; }
pre.numberSource code > span > a:first-child::before
{ content: counter(source-line);
position: relative; left: -1em; text-align: right; vertical-align: baseline;
border: none; display: inline-block;
-webkit-touch-callout: none; -webkit-user-select: none;
-khtml-user-select: none; -moz-user-select: none;
-ms-user-select: none; user-select: none;
padding: 0 4px; width: 4em;
color: #aaaaaa;
}
pre.numberSource { margin-left: 3em; border-left: 1px solid #aaaaaa; padding-left: 4px; }
div.sourceCode
{ }
@media screen {
pre > code.sourceCode > span > a:first-child::before { text-decoration: underline; }
}
code span.al { color: #ff0000; font-weight: bold; } 
code span.an { color: #60a0b0; font-weight: bold; font-style: italic; } 
code span.at { color: #7d9029; } 
code span.bn { color: #40a070; } 
code span.bu { color: #008000; } 
code span.cf { color: #007020; font-weight: bold; } 
code span.ch { color: #4070a0; } 
code span.cn { color: #880000; } 
code span.co { color: #60a0b0; font-style: italic; } 
code span.cv { color: #60a0b0; font-weight: bold; font-style: italic; } 
code span.do { color: #ba2121; font-style: italic; } 
code span.dt { color: #902000; } 
code span.dv { color: #40a070; } 
code span.er { color: #ff0000; font-weight: bold; } 
code span.ex { } 
code span.fl { color: #40a070; } 
code span.fu { color: #06287e; } 
code span.im { color: #008000; font-weight: bold; } 
code span.in { color: #60a0b0; font-weight: bold; font-style: italic; } 
code span.kw { color: #007020; font-weight: bold; } 
code span.op { color: #666666; } 
code span.ot { color: #007020; } 
code span.pp { color: #bc7a00; } 
code span.sc { color: #4070a0; } 
code span.ss { color: #bb6688; } 
code span.st { color: #4070a0; } 
code span.va { color: #19177c; } 
code span.vs { color: #4070a0; } 
code span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } 
</style>
<script>
// apply pandoc div.sourceCode style to pre.sourceCode instead
(function() {
  var sheets = document.styleSheets;
  for (var i = 0; i < sheets.length; i++) {
    if (sheets[i].ownerNode.dataset["origin"] !== "pandoc") continue;
    try { var rules = sheets[i].cssRules; } catch (e) { continue; }
    var j = 0;
    while (j < rules.length) {
      var rule = rules[j];
      // check if there is a div.sourceCode rule
      if (rule.type !== rule.STYLE_RULE || rule.selectorText !== "div.sourceCode") {
        j++;
        continue;
      }
      var style = rule.style.cssText;
      // check if color or background-color is set
      if (rule.style.color === '' && rule.style.backgroundColor === '') {
        j++;
        continue;
      }
      // replace div.sourceCode by a pre.sourceCode rule
      sheets[i].deleteRule(j);
      sheets[i].insertRule('pre.sourceCode{' + style + '}', j);
    }
  }
})();
</script>




<style type="text/css">body {
background-color: #fff;
margin: 1em auto;
max-width: 700px;
overflow: visible;
padding-left: 2em;
padding-right: 2em;
font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 14px;
line-height: 1.35;
}
#TOC {
clear: both;
margin: 0 0 10px 10px;
padding: 4px;
width: 400px;
border: 1px solid #CCCCCC;
border-radius: 5px;
background-color: #f6f6f6;
font-size: 13px;
line-height: 1.3;
}
#TOC .toctitle {
font-weight: bold;
font-size: 15px;
margin-left: 5px;
}
#TOC ul {
padding-left: 40px;
margin-left: -1.5em;
margin-top: 5px;
margin-bottom: 5px;
}
#TOC ul ul {
margin-left: -2em;
}
#TOC li {
line-height: 16px;
}
table {
margin: 1em auto;
border-width: 1px;
border-color: #DDDDDD;
border-style: outset;
border-collapse: collapse;
}
table th {
border-width: 2px;
padding: 5px;
border-style: inset;
}
table td {
border-width: 1px;
border-style: inset;
line-height: 18px;
padding: 5px 5px;
}
table, table th, table td {
border-left-style: none;
border-right-style: none;
}
table thead, table tr.even {
background-color: #f7f7f7;
}
p {
margin: 0.5em 0;
}
blockquote {
background-color: #f6f6f6;
padding: 0.25em 0.75em;
}
hr {
border-style: solid;
border: none;
border-top: 1px solid #777;
margin: 28px 0;
}
dl {
margin-left: 0;
}
dl dd {
margin-bottom: 13px;
margin-left: 13px;
}
dl dt {
font-weight: bold;
}
ul {
margin-top: 0;
}
ul li {
list-style: circle outside;
}
ul ul {
margin-bottom: 0;
}
pre, code {
background-color: #f7f7f7;
border-radius: 3px;
color: #333;
white-space: pre-wrap; 
}
pre {
border-radius: 3px;
margin: 5px 0px 10px 0px;
padding: 10px;
}
pre:not([class]) {
background-color: #f7f7f7;
}
code {
font-family: Consolas, Monaco, 'Courier New', monospace;
font-size: 85%;
}
p > code, li > code {
padding: 2px 0px;
}
div.figure {
text-align: center;
}
img {
background-color: #FFFFFF;
padding: 2px;
border: 1px solid #DDDDDD;
border-radius: 3px;
border: 1px solid #CCCCCC;
margin: 0 5px;
}
h1 {
margin-top: 0;
font-size: 35px;
line-height: 40px;
}
h2 {
border-bottom: 4px solid #f7f7f7;
padding-top: 10px;
padding-bottom: 2px;
font-size: 145%;
}
h3 {
border-bottom: 2px solid #f7f7f7;
padding-top: 10px;
font-size: 120%;
}
h4 {
border-bottom: 1px solid #f7f7f7;
margin-left: 8px;
font-size: 105%;
}
h5, h6 {
border-bottom: 1px solid #ccc;
font-size: 105%;
}
a {
color: #0033dd;
text-decoration: none;
}
a:hover {
color: #6666ff; }
a:visited {
color: #800080; }
a:visited:hover {
color: #BB00BB; }
a[href^="http:"] {
text-decoration: underline; }
a[href^="https:"] {
text-decoration: underline; }

code > span.kw { color: #555; font-weight: bold; } 
code > span.dt { color: #902000; } 
code > span.dv { color: #40a070; } 
code > span.bn { color: #d14; } 
code > span.fl { color: #d14; } 
code > span.ch { color: #d14; } 
code > span.st { color: #d14; } 
code > span.co { color: #888888; font-style: italic; } 
code > span.ot { color: #007020; } 
code > span.al { color: #ff0000; font-weight: bold; } 
code > span.fu { color: #900; font-weight: bold; } 
code > span.er { color: #a61717; background-color: #e3d2d2; } 
</style>




</head>

<body>




<h1 class="title toc-ignore">Using renv with Docker</h1>



<p>While renv can help capture the state of your R library at some point
in time, there are still other aspects of the system that can influence
the runtime behavior of your R application. In particular, the same R
code can produce different results depending on:</p>
<ul>
<li>The operating system in use,</li>
<li>The compiler flags used when R and packages are built,</li>
<li>The LAPACK / BLAS system(s) in use,</li>
<li>The versions of system libraries installed and in use,</li>
</ul>
<p>And so on. <a href="https://www.docker.com/">Docker</a> is a tool
that can help solve this problem through the use of
<strong>containers</strong>. Very roughly speaking, one can think of a
container as a small, self-contained system within which different
applications can be run. Using Docker, one can declaratively state how a
container should be built (what operating system it should use, and what
system software should be installed within), and use that system to run
applications. (For more details, please see <a href="https://environments.rstudio.com/docker" class="uri">https://environments.rstudio.com/docker</a>.)</p>
<p>Using Docker and renv together, one can then ensure that both the
underlying system, alongside the required R packages, are fixed and
constant for a particular application.</p>
<p>The main challenges in using Docker with renv are:</p>
<ul>
<li><p>Ensuring that the renv cache is visible to Docker containers,
and</p></li>
<li><p>Ensuring that required R package dependencies are available at
runtime.</p></li>
</ul>
<p>This vignette will assume you are already familiar with Docker; if
you are not yet familiar with Docker, the <a href="https://docs.docker.com/">Docker Documentation</a> provides a
thorough introduction. To learn more about using Docker to manage R
environments, visit <a href="https://environments.rstudio.com/docker.html">environments.rstudio.com</a>.</p>
<p>We’ll discuss two strategies for using renv with Docker:</p>
<ol style="list-style-type: decimal">
<li>Using renv to install packages when the Docker image is
generated;</li>
<li>Using renv to install packages when Docker containers are run.</li>
</ol>
<p>We’ll also explore the pros and cons of each strategy.</p>
<div id="creating-docker-images-with-renv" class="section level2">
<h2>Creating Docker images with renv</h2>
<p>With Docker, <a href="https://docs.docker.com/engine/reference/builder/">Dockerfiles</a>
are used to define new images. Dockerfiles can be used to declaratively
specify how a Docker image should be created. A Docker image captures
the state of a machine at some point in time – e.g., a Linux operating
system after downloading and installing R 4.5. Docker containers can be
created using that image as a base, allowing different independent
applications to run using the same pre-defined machine state.</p>
<p>First, you’ll need to get renv installed on your Docker image. For
example, you could install the latest release of renv from CRAN:</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode dockerfile"><code class="sourceCode dockerfile"><span id="cb1-1"><a href="#cb1-1" tabindex="-1"></a><span class="kw">RUN</span> <span class="ex">R</span> <span class="at">-e</span> <span class="st">&quot;install.packages(&#39;renv&#39;, repos = c(CRAN = &#39;https://cloud.r-project.org&#39;))&quot;</span></span></code></pre></div>
<p>Alternatively, if you need to use the development version of renv,
you could use:</p>
<div class="sourceCode" id="cb2"><pre class="sourceCode dockerfile"><code class="sourceCode dockerfile"><span id="cb2-1"><a href="#cb2-1" tabindex="-1"></a><span class="kw">RUN</span> <span class="ex">R</span> <span class="at">-e</span> <span class="st">&quot;install.packages(&#39;renv&#39;, repos = &#39;https://rstudio.r-universe.dev&#39;)&quot;</span></span></code></pre></div>
<p>Next, we’ll copy <code>renv.lock</code> into the container:</p>
<div class="sourceCode" id="cb3"><pre class="sourceCode dockerfile"><code class="sourceCode dockerfile"><span id="cb3-1"><a href="#cb3-1" tabindex="-1"></a><span class="kw">WORKDIR</span> /project</span>
<span id="cb3-2"><a href="#cb3-2" tabindex="-1"></a><span class="kw">COPY</span> renv.lock renv.lock</span></code></pre></div>
<p>Now, we <code>renv::restore()</code> to install those packages. At
this stage, you’ll need to decide which of R’s library paths you’d like
to use for pacakge installation. (See <code>?.libPaths</code> for more
information.) There are a couple of options available:</p>
<div id="use-the-default-library-paths" class="section level3">
<h3>Use the default library paths</h3>
<p>This method is appropriate if you’d like these packages to be visible
to all R processes launched using this image, and can be done via:</p>
<div class="sourceCode" id="cb4"><pre class="sourceCode dockerfile"><code class="sourceCode dockerfile"><span id="cb4-1"><a href="#cb4-1" tabindex="-1"></a><span class="kw">RUN</span> <span class="ex">R</span> <span class="at">-e</span> <span class="st">&quot;renv::restore()&quot;</span></span></code></pre></div>
<p>Note that this method may fail if R’s default library paths are not
on a writable volume in the Docker image. If this is the case, consider
one of the alternatives below.</p>
</div>
<div id="use-the-default-project-library-path" class="section level3">
<h3>Use the default project library path</h3>
<p>If you want to use renv’s default project-local library path, you’ll
need to initialize the project within the Docker container as an renv
project. This can be done with:</p>
<div class="sourceCode" id="cb5"><pre class="sourceCode dockerfile"><code class="sourceCode dockerfile"><span id="cb5-1"><a href="#cb5-1" tabindex="-1"></a><span class="kw">RUN</span> <span class="ex">R</span> <span class="at">-s</span> <span class="at">-e</span> <span class="st">&quot;renv::init(bare = TRUE)&quot;</span></span>
<span id="cb5-2"><a href="#cb5-2" tabindex="-1"></a><span class="kw">RUN</span> <span class="ex">R</span> <span class="at">-s</span> <span class="at">-e</span> <span class="st">&quot;renv::restore()&quot;</span></span></code></pre></div>
<p>Or, alternatively, if you already have a project autoloader +
settings available – e.g. because you’re creating a Docker image from an
existing renv project – you could use:</p>
<div class="sourceCode" id="cb6"><pre class="sourceCode dockerfile"><code class="sourceCode dockerfile"><span id="cb6-1"><a href="#cb6-1" tabindex="-1"></a><span class="kw">RUN</span> <span class="fu">mkdir</span> <span class="at">-p</span> renv</span>
<span id="cb6-2"><a href="#cb6-2" tabindex="-1"></a><span class="kw">COPY</span> .Rprofile .Rprofile</span>
<span id="cb6-3"><a href="#cb6-3" tabindex="-1"></a><span class="kw">COPY</span> renv/activate.R renv/activate.R</span>
<span id="cb6-4"><a href="#cb6-4" tabindex="-1"></a><span class="kw">COPY</span> renv/settings.json renv/settings.json</span>
<span id="cb6-5"><a href="#cb6-5" tabindex="-1"></a><span class="kw">RUN</span> <span class="ex">R</span> <span class="at">-s</span> <span class="at">-e</span> <span class="st">&quot;renv::restore()&quot;</span></span></code></pre></div>
<p>Note that in this mode, the installed packages would only be visible
to R sessions launched using <code>/project</code> as the working
directory. This will be the default behavior as long as
<code>WORKDIR</code> is not changed, but it’s important to keep this in
mind.</p>
</div>
<div id="use-a-custom-library-path" class="section level3">
<h3>Use a custom library path</h3>
<p>If you’d like to fully customize the library path used, the simplest
approach is likely to use the <code>RENV_PATHS_LIBRARY</code>
environment variable. This mimics the above approach, but customizes the
library paths used by renv. For example:</p>
<div class="sourceCode" id="cb7"><pre class="sourceCode dockerfile"><code class="sourceCode dockerfile"><span id="cb7-1"><a href="#cb7-1" tabindex="-1"></a><span class="kw">ENV</span> RENV_PATHS_LIBRARY=renv/library</span>
<span id="cb7-2"><a href="#cb7-2" tabindex="-1"></a><span class="kw">RUN</span> <span class="ex">R</span> <span class="at">-s</span> <span class="at">-e</span> <span class="st">&quot;renv::init(bare = TRUE)&quot;</span></span>
<span id="cb7-3"><a href="#cb7-3" tabindex="-1"></a><span class="kw">RUN</span> <span class="ex">R</span> <span class="at">-s</span> <span class="at">-e</span> <span class="st">&quot;renv::restore()&quot;</span></span></code></pre></div>
<p>Alternatively, you could manage the library paths yourself via
<code>.libPaths()</code> – see <code>?.libPaths</code> in R for more
inforamtion.</p>
</div>
</div>
<div id="speeding-up-package-installations" class="section level2">
<h2>Speeding up package installations</h2>
<p>The previously-described approaches are useful if you have multiple
applications with identical package requirements. In this case, a single
image containing this identical package library could serve as the
parent image for several containerized applications.</p>
<p>However, <code>renv::restore()</code> is slow – it needs to download
and install packages, which can take some time. Thus, some care is
required to efficiently make use of the renv cache for projects that
require:</p>
<ol style="list-style-type: decimal">
<li><p>Building an image multiple times (e.g., to debug the production
application as source code is updated), or</p></li>
<li><p>Calling <code>renv::restore()</code> each time the container is
run.</p></li>
</ol>
<p>The former process can be sped up using multi-stage builds, the
latter by dynamically provisioning R Libraries, as described below.</p>
<div id="multi-stage-builds" class="section level3">
<h3>Multi-stage builds</h3>
<p>For projects that require repeatedly building an image, <a href="https://docs.docker.com/build/building/multi-stage/">multi-stage
builds</a> can be used to speed up the build process. With multi-stage
builds, multiple FROM statements are used in the Dockerfile and files
can be copied across build stages.</p>
<p>This approach can be leveraged to generate more efficient builds by
dedicating a first stage build to package synchronization and a second
stage build to copying files and executing code that may need to be
updated often across builds (e.g., code that needs to be debugged in the
container).</p>
<p>To implement a two stage build, the following code could be used as
part of a Dockerfile.</p>
<div class="sourceCode" id="cb8"><pre class="sourceCode dockerfile"><code class="sourceCode dockerfile"><span id="cb8-1"><a href="#cb8-1" tabindex="-1"></a><span class="kw">FROM</span> &lt;parent-image&gt; <span class="kw">AS</span> base</span>
<span id="cb8-2"><a href="#cb8-2" tabindex="-1"></a></span>
<span id="cb8-3"><a href="#cb8-3" tabindex="-1"></a><span class="co"># intialize the project; assuming renv infrastructure available</span></span>
<span id="cb8-4"><a href="#cb8-4" tabindex="-1"></a><span class="kw">WORKDIR</span> /project</span>
<span id="cb8-5"><a href="#cb8-5" tabindex="-1"></a><span class="kw">RUN</span> <span class="fu">mkdir</span> <span class="at">-p</span> renv</span>
<span id="cb8-6"><a href="#cb8-6" tabindex="-1"></a><span class="kw">COPY</span> renv.lock renv.lock</span>
<span id="cb8-7"><a href="#cb8-7" tabindex="-1"></a><span class="kw">COPY</span> .Rprofile .Rprofile</span>
<span id="cb8-8"><a href="#cb8-8" tabindex="-1"></a><span class="kw">COPY</span> renv/activate.R renv/activate.R</span>
<span id="cb8-9"><a href="#cb8-9" tabindex="-1"></a><span class="kw">COPY</span> renv/settings.dcf renv/settings.dcf</span>
<span id="cb8-10"><a href="#cb8-10" tabindex="-1"></a></span>
<span id="cb8-11"><a href="#cb8-11" tabindex="-1"></a><span class="co"># change default location of cache to project folder</span></span>
<span id="cb8-12"><a href="#cb8-12" tabindex="-1"></a><span class="kw">RUN</span> <span class="fu">mkdir</span> renv/.cache</span>
<span id="cb8-13"><a href="#cb8-13" tabindex="-1"></a><span class="kw">ENV</span> RENV_PATHS_CACHE=renv/.cache</span>
<span id="cb8-14"><a href="#cb8-14" tabindex="-1"></a></span>
<span id="cb8-15"><a href="#cb8-15" tabindex="-1"></a><span class="co"># restore </span></span>
<span id="cb8-16"><a href="#cb8-16" tabindex="-1"></a><span class="kw">RUN</span> <span class="ex">R</span> <span class="at">-s</span> <span class="at">-e</span> <span class="st">&quot;renv::restore()&quot;</span></span></code></pre></div>
<p>The above code uses
<code>FROM &lt;parent-image&gt; AS &lt;name&gt;</code> to name the first
stage of the build <code>base</code>. Here,
<code>&lt;parent-image&gt;</code> should be replaced with an appropriate
image name.</p>
<p>Subsequently, the code uses approach 2 (described above) to copy the
auto-loader to the project directory in the image. It additionally
creates the <code>renv/.cache</code> directory that is to be used as the
renv cache.</p>
<p>The second stage of the build is defined by adding the following code
to the same Dockerfile, below the previous code chunk.</p>
<div class="sourceCode" id="cb9"><pre class="sourceCode dockerfile"><code class="sourceCode dockerfile"><span id="cb9-1"><a href="#cb9-1" tabindex="-1"></a><span class="kw">FROM</span> &lt;parent-image&gt;</span>
<span id="cb9-2"><a href="#cb9-2" tabindex="-1"></a></span>
<span id="cb9-3"><a href="#cb9-3" tabindex="-1"></a><span class="kw">WORKDIR</span> /project</span>
<span id="cb9-4"><a href="#cb9-4" tabindex="-1"></a><span class="kw">COPY</span> <span class="op">--from=base</span> /project .</span>
<span id="cb9-5"><a href="#cb9-5" tabindex="-1"></a></span>
<span id="cb9-6"><a href="#cb9-6" tabindex="-1"></a><span class="co"># add commands that need to be debugged below</span></span></code></pre></div>
<p>Here, <code>&lt;parent-image&gt;</code> could be the same as the
parent image of <code>base</code>, but does not have to be (see <a href="https://docs.docker.com/build/building/multi-stage/">documentation</a>
for more details).</p>
<p>The key line is the <code>COPY</code> command, which specifies that
the contents of <code>/project</code> directory from the
<code>base</code> image are copied into the <code>/project</code>
directory of this image.</p>
<p>Any commands that will change frequently across builds could be
included below the <code>COPY</code> command. If only this code
associated with the second stage build is updated then
<code>renv::restore()</code> will not be called again at build time.
Instead, the layers associated with the <code>base</code> image will be
loaded from Docker’s cache, thereby saving significant time in build
process.</p>
<p>In fact, <code>renv::restore()</code> will only be called when the
<code>base</code> image needs to be rebuilt (e.g., when changes are made
to <code>renv.lock</code>). Docker’s cache system is generally good at
understanding the dependencies of images. However, if you find that the
<code>base</code> image is not updating as expected, it is possible to
manually enforce a clean build by including the <code>--no-cache</code>
option in the call to <code>docker build</code>.</p>
</div>
<div id="dynamically-provisioning-r-libraries-with-renv" class="section level3">
<h3>Dynamically Provisioning R Libraries with renv</h3>
<p>However, on occasion, one will have multiple applications built from
a single base image, but each application will have its own independent
R package requirements. In this case, rather than including the package
dependencies in the image itself, it would be preferable for each
container to provision its own library at runtime, based on that
application’s <code>renv.lock</code> lockfile.</p>
<p>In effect, this is as simple as ensuring that
<code>renv::restore()</code> happens at container runtime, rather than
image build time. However, on its own, <code>renv::restore()</code> is
slow – it needs to download and install packages, which could take
prohibitively long if an application needs to be run repeatedly.</p>
<p>The renv package cache can be used to help ameliorate this issue.
When the cache is enabled, whenever renv attempts to install or restore
an R package, it first checks to see whether that package is already
available within the renv cache. If it is, that instance of the package
is linked into the project library. Otherwise, the package is first
installed into the renv cache, and then that newly-installed copy is
linked for use in the project.</p>
<p>In effect, if the renv cache is available, you should only need to
pay the cost of package installation once – after that, the
newly-installed package will be available for re-use across different
projects. At the same time, each project’s library will remain
independent and isolated from one another, so installing a package
within one container won’t affect another container.</p>
<p>However, by default, each Docker container will have its own
independent filesystem. Ideally, we’d like for <em>all</em> containers
launched from a particular image to have access to the same renv cache.
To accomplish this, we’ll have to tell each container to use an renv
cache located on a shared mount.</p>
<p>In sum, if we’d like to allow for runtime provisioning of R package
dependencies, we will need to ensure the renv cache is located on a
shared volume, which is visible to any containers launched. We will
accomplish this by:</p>
<ol style="list-style-type: decimal">
<li><p>Setting the <code>RENV_PATHS_CACHE</code> environment variable,
to tell the instance of renv running in each container where the global
cache lives;</p></li>
<li><p>Telling Docker to mount some filesystem location from the host
filesystem, at some location (<code>RENV_PATHS_CACHE_HOST</code>), to a
container-specific location
(<code>RENV_PATHS_CACHE_CONTAINER</code>).</p></li>
</ol>
<p>For example, if you had a container running a Shiny application:</p>
<div class="sourceCode" id="cb10"><pre class="sourceCode dockerfile"><code class="sourceCode dockerfile"><span id="cb10-1"><a href="#cb10-1" tabindex="-1"></a><span class="co"># the location of the renv cache on the host machine</span></span>
<span id="cb10-2"><a href="#cb10-2" tabindex="-1"></a>RENV_PATHS_CACHE_HOST=/opt/local/renv/cache</span>
<span id="cb10-3"><a href="#cb10-3" tabindex="-1"></a></span>
<span id="cb10-4"><a href="#cb10-4" tabindex="-1"></a><span class="co"># where the cache should be mounted in the container</span></span>
<span id="cb10-5"><a href="#cb10-5" tabindex="-1"></a>RENV_PATHS_CACHE_CONTAINER=/renv/cache</span>
<span id="cb10-6"><a href="#cb10-6" tabindex="-1"></a></span>
<span id="cb10-7"><a href="#cb10-7" tabindex="-1"></a><span class="co"># run the container with the host cache mounted in the container</span></span>
<span id="cb10-8"><a href="#cb10-8" tabindex="-1"></a>docker run --rm <span class="op">\</span></span>
<span id="cb10-9"><a href="#cb10-9" tabindex="-1"></a>    -e <span class="st">&quot;RENV_PATHS_CACHE=${RENV_PATHS_CACHE_CONTAINER}&quot;</span> <span class="op">\</span></span>
<span id="cb10-10"><a href="#cb10-10" tabindex="-1"></a>    -v <span class="st">&quot;${RENV_PATHS_CACHE_HOST}:${RENV_PATHS_CACHE_CONTAINER}&quot;</span> <span class="op">\</span></span>
<span id="cb10-11"><a href="#cb10-11" tabindex="-1"></a>    -p 14618:14618 <span class="op">\</span></span>
<span id="cb10-12"><a href="#cb10-12" tabindex="-1"></a>    R -s -e <span class="st">&#39;renv::restore(); shiny::runApp(host = &quot;0.0.0.0&quot;, port = 14618)&#39;</span></span></code></pre></div>
<p>Note that the invocation above assumes that the project has already
been initialized either via calling <code>renv::init()</code> or by
copying the requisite <code>renv</code> project infrastructure. With
this, any calls to renv APIs within the created docker container will
have access to the mounted cache. The first time you run a container,
renv will likely need to populate the cache, and so some time will be
spent downloading and installing the required packages. Subsequent runs
will be much faster, as renv will be able to reuse the global package
cache.</p>
<p>The primary downside with this approach compared to the image-based
approach is that it requires you to modify how containers are created,
and requires a bit of extra orchestration in how containers are
launched. However, once the renv cache is active, newly-created
containers will launch very quickly, and a single image can then be used
as a base for a myriad of different containers and applications, each
with their own independent package dependencies.</p>
</div>
</div>
<div id="handling-the-renv-autoloader" class="section level2">
<h2>Handling the renv autoloader</h2>
<p>When <code>R</code> is launched within a project folder, the renv
auto-loader (if present) will attempt to download and install renv into
the project library if it’s not available. Depending on how your Docker
container is configured, this could fail. For example:</p>
<div class="sourceCode" id="cb11"><pre class="sourceCode sh"><code class="sourceCode bash"><span id="cb11-1"><a href="#cb11-1" tabindex="-1"></a><span class="ex">Error</span> installing renv:</span>
<span id="cb11-2"><a href="#cb11-2" tabindex="-1"></a><span class="ex">======================</span></span>
<span id="cb11-3"><a href="#cb11-3" tabindex="-1"></a><span class="ex">ERROR:</span> unable to create <span class="st">&#39;/usr/local/pipe/renv/library/master/R-4.0/x86_64-pc-linux-gnu/renv&#39;</span></span>
<span id="cb11-4"><a href="#cb11-4" tabindex="-1"></a><span class="ex">Warning</span> messages:</span>
<span id="cb11-5"><a href="#cb11-5" tabindex="-1"></a><span class="ex">1:</span> In system2<span class="er">(</span><span class="ex">r,</span> args, stdout = TRUE, stderr = TRUE<span class="kw">)</span> <span class="bu">:</span></span>
<span id="cb11-6"><a href="#cb11-6" tabindex="-1"></a>  <span class="ex">running</span> command <span class="st">&#39;&#39;</span>/usr/lib/R/bin/R<span class="st">&#39; --vanilla CMD INSTALL -l &#39;</span>renv/library/master/R-4.0/x86_64-pc-linux-gnu<span class="st">&#39; &#39;</span>/tmp/RtmpwM7ooh/renv_0.12.2.tar.gz<span class="st">&#39; 2&gt;&amp;1&#39;</span> had status 1</span>
<span id="cb11-7"><a href="#cb11-7" tabindex="-1"></a><span class="ex">2:</span> Failed to find an renv installation: the project will not be loaded.</span>
<span id="cb11-8"><a href="#cb11-8" tabindex="-1"></a><span class="ex">Use</span> <span class="kw">`</span><span class="fu">renv::activate()</span><span class="kw">`</span> to re-initialize the project.</span></code></pre></div>
<p>Bootstrapping renv into the project library might be unnecessary for
you. If that is the case, then you can avoid this behavior by launching
R with the <code>--vanilla</code> flag set; for example:</p>
<div class="sourceCode" id="cb12"><pre class="sourceCode sh"><code class="sourceCode bash"><span id="cb12-1"><a href="#cb12-1" tabindex="-1"></a><span class="ex">R</span> <span class="at">--vanilla</span> <span class="at">-s</span> <span class="at">-e</span> <span class="st">&#39;renv::restore()&#39;</span></span></code></pre></div>
</div>



<!-- code folding -->


<!-- dynamically load mathjax for compatibility with self-contained -->
<script>
  (function () {
    var script = document.createElement("script");
    script.type = "text/javascript";
    script.src  = "https://mathjax.rstudio.com/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML";
    document.getElementsByTagName("head")[0].appendChild(script);
  })();
</script>

</body>
</html>
